#include <QtCore/QUuid>
#include <QtGui/QApplication>
#include <Box2D/Box2D.h>
#include <QyDebug.h>
#include <QyMath.h>
#include <QpwGeometry.h>
#include "QpwDecorationObject.h"
#include "QpwDynamicObject.h"
#include "QpwPackages.h"
#include "QpwPhysics.h"
#include "QpwStaticObject.h"
#include "QpwWorld.h"

using namespace QpwWorld;

/**************************************************************************************************/

MovingController::MovingController( QpwWorld::DynamicObject * dynamicObject )
    : rotationSpeed(M_PI/4.0),
    p_dynamicObject(0), body(0),
    p_direction(0.0),
    p_movingDirection(0),
    p_movingSpeed(1),
    p_moving(false),
    p_rotation(false)
{

    setDynamicObject(dynamicObject);

}

qreal MovingController::realMovingDirection() const {

//     return body ? p_direction + p_movingDirection : 0;
    return body ? p_movingDirection : 0;
}

void MovingController::setDirection( qreal direction ) {

    p_rotation = true;//( p_direction != direction );
    p_direction = direction;

}

void MovingController::setMovingDirection( qreal movingDirection ) {

    p_movingDirection = movingDirection;
    if (p_moving)
        updateMoving();

}

inline qreal calcSpeed( qreal value, qreal time ) {

    return value * ( time / 1000.0 );
}

void MovingController::advance( qreal elapsedTime ) {

    if (!body)
        return;

    if (p_rotation) {

//         qreal addValue = calcSpeed( rotationSpeed, elapsedTime );
//         qreal angle = body->GetAngle();

//         if ( p_direction == angle ) {
//         } else if ( p_direction > angle ) {
//         } else {
//         }
//         body->SetAngle(p_direction);
//         body->SetAngularVelocity(p_direction);
        body->SetTransform( body->GetPosition(), -p_direction - M_PI );
        body->SetAwake(true);
        p_rotation = false;

    }

    if (p_moving) {

//         b2Contact * contact = body->GetWorld()->GetContactList();

//         while (contact) {

//             if ( contact->GetFixtureA()->GetBody() == body
//                 || contact->GetFixtureB()->GetBody() == body )
//             {

//                 p_moving = false;
//                 break;
//             }

//             contact = contact->GetNext();

//         }

        b2Vec2 pos = body->GetPosition();
        QyMath::Vector vec( pos.x, pos.y );

        vec.move( p_movingSpeed, QyMath::degress( realMovingDirection() ) );
        body->ApplyForce( b2Vec2( vec.p[0], vec.p[1] ), pos );

    }

}

void MovingController::beginMoving() {

    p_moving = true;
    updateMoving();

}

void MovingController::endMoving() {

    p_moving = false;
    updateMoving();

}

void MovingController::setDynamicObject( QpwWorld::DynamicObject * dynamicObject ) {

    p_dynamicObject = dynamicObject;
    body = ( p_dynamicObject && p_dynamicObject->physicsData() )
        ? p_dynamicObject->physicsData()->body()
        : 0;

}

void MovingController::setMovingSpeed( qreal movingSpeed ) {

    p_movingSpeed = movingSpeed;
    if (p_moving)
        updateMoving();

}

void MovingController::updateMoving() {

    if (!body)
        return;

    if (p_moving) {

//         b2Vec2 pos = body->GetPosition();
//         QyMath::Vector vec( pos.x, pos.y );

//         vec.move( p_movingSpeed, realMovingDirection() );
//         body->SetLinearVelocity( b2Vec2( 0, 0 ) );
//         body->SetLinearVelocity( b2Vec2( vec.p[0], vec.p[1] ) );

    } else {

//         body->SetLinearVelocity( b2Vec2( 0, 0 ) );

    }

}

/**************************************************************************************************/

Processor::Processor( QObject * parent ) : QObject(parent), p_timerId(0) {

    p_physicWorld = new b2World( b2Vec2( 0, 0 ), true );

    qDebug("World Processor: World processor created.");

}

Processor::~Processor() {

    delete p_physicWorld;
    qDebug("World Processor: World processor destroyed.");

}
/*old
QpwPhysics::StaticObjectData * Processor::createStaticObjectData( QpwWorld::StaticObject * object ) {

    if (!object) {

        qWarning("Processor: can't create physic data for 0 object");

        return 0;
    }

    Geometry geo = object->geometry();

    if ( geo.isEmpty() && geo.subGeometry.isEmpty() ) {

        qWarning("Processor: can't create physic data for object without geometry");

        return 0;
    }

    b2BodyDef bodyDef;

    bodyDef.position.Set( geo.center.x(), geo.center.y() );
    b2Body * body = p_physicWorld->CreateBody(&bodyDef);

    if ( geo.count() ) {

        addBodyGeometry( body, geo );

    }

    if ( geo.subGeometry.count() ) {

        foreach ( QpwWorld::Geometry subGeo, geo.subGeometry )
            addBodyGeometry( body, subGeo );

    }

    return new QpwPhysics::StaticObjectData(body);
}

QpwPhysics::DynamicObjectData * createDynamicObjectData( QpwWorld::DynamicObject * object ) {

    return 0;
}

void Processor::addObject( QpwWorld::DecorationObject * object ) {

    if (!object) {

        qWarning("World Processor: can't add 0 object in world");

        return;
    }

    object->setUuid( QUuid::createUuid() );

    switch ( object->baseType() ) {

        case QpwWorld::DecorationObject::DecorationObjectType:
            // nothing
            break;


        case QpwWorld::DecorationObject::StaticObjectType:
        case QpwWorld::DecorationObject::QuasiStaticObjectType:
            if ( QpwWorld::StaticObject * staticBased
                = dynamic_cast<QpwWorld::StaticObject *>(object) )
            {

                QpwPhisics::StaticObjectData * phisicsData = staticBased->phisicsData();

                if (!physicData) {

                    phisicsData = createStaticObjectData(staticBased);
                    if (!phisicsData) {

                        qWarning("World Processor: Can't add object without physics data");

                        return;
                    }

                    staticBased->setPhysicsData(phisicsData);

                }

            }
            break;


        case QpwWorld::DecorationObject::DynamicObjectType:
            if ( QpwWorld::DynamicObject * dynamicObject
                = dynamic_cast<QpwWorld::DynamicObject *>(object) )
            {

                QpwPhisics::StaticObjectData * phisicsData = dynamicObject->phisicsData();

                if (!physicData) {

                    phisicsData = createDynamicObjectData(staticBased);
                    if (!phisicsData) {

                        qWarning("World Processor: Can't add object without physics data");

                        return;
                    }

                    dynamicObject->setPhysicsData(phisicsData);

                }

            }
            break;

        default:
            qWarning("World Processor: Detected unknow oject type.");
            break;

    }

    p_objects.append(object);

}

void Processor::removeObject( QpwWorld::DecorationObject * object ) {

    if (!object) {

        qWarning("World Processor: can't remove 0 object from world");

        return;
    }

    switch ( object->baseType() ) {

        case QpwWorld::DecorationObject::DecorationObjectType:
            // nothing
            break;

        case QpwWorld::DecorationObject::StaticObjectType:
        case QpwWorld::DecorationObject::QuasiStaticObjectType:
        case QpwWorld::DecorationObject::DynamicObjectType:
            if ( QpwWorld::StaticObject * staticBased
                = dynamic_cast<QpwWorld::StaticObject *>(object) )
            {

                QpwPhisics::StaticObjectData * phisicsData = staticBased->phisicsData();

                if (physicData) {

                    p_physicWorld->DestroyBody( phisicsData->body() );
                    staticBased->setPhysicsData(0);
                    delete phisicsData;

                }

            }
            break;

        default:
            qWarning("World Processor: Detected unknow oject type.");
            break;

    }

    p_objects.removeOne(object);

}
*/

DecorationObject * Processor::createObject( const QpwSubType & subtype, const QUuid & uuid ) {

    DecorationObject * object = 0;

    switch (subtype.baseType) {

    case DecorationObject::DecorationObjectType:
        object = new DecorationObject( subtype, styleDatabase );
        break;

    case DecorationObject::StaticObjectType:
    case DecorationObject::QuasiStaticObjectType:
        {

            StaticObject * staticObject = new StaticObject( subtype, styleDatabase );
            staticObject->setPhysicsData( QpwPhysics::ObjectData::fromSubType( p_physicWorld,
                subtype, geometryDatabase ) );
            object = staticObject;

        }
        break;

    case DecorationObject::DynamicObjectType:
        {

            DynamicObject * dynamicObject = new DynamicObject( subtype, styleDatabase );
            dynamicObject->setPhysicsData( QpwPhysics::ObjectData::fromSubType( p_physicWorld,
                subtype, geometryDatabase ) );
            object = dynamicObject;

        }

    }

    if (object) {

        QUuid n_uuid = uuid.isNull() ? QUuid::createUuid() : uuid;
        objects[n_uuid] = object;
        object->setUuid(n_uuid);
        send( &QpwPackages::MakeObject( n_uuid, subtype.uuid ) );

    }

    return object;
}

void Processor::removeObject( QpwWorld::DecorationObject * object ) {

    QyDbgLocation();

    qDebug( "World processor: Remove object %s", qPrintable( object->uuid().toString() ) );

    objects.remove( object->uuid() );

    if ( object->baseType() != DecorationObject::DecorationObjectType ) {

        qDebug( "  is physics object" );
        StaticObject * staticObject = (StaticObject *) object;

        if ( staticObject->physicsData() ) {

            qDebug( "  removing phys data" );
            p_physicWorld->DestroyBody( staticObject->physicsData()->body() );
            delete staticObject->physicsData();

        } else {

            qDebug( "  phys data not found" );

        }

    }

    send( &QpwPackages::DestroyObject( object->uuid() ) );
    delete object;

}

bool Processor::loadData( const QString & dataPath ) {

    bool ok = true;
    ok = ok && styleDatabase.load( dataPath + "/styles.xml" );
    ok = ok && subTypeDatabase.load( dataPath + "/subtypes.xml" );

    return ok;
}

void Processor::start( int interval ) {

    if ( interval <= 0 ) {

        interval = qRound(1000/60.0);
        qWarning( "World Processor: use default processing interval: %d ms", interval );

    }

    if (p_timerId)
        killTimer(p_timerId);

    p_timerId = startTimer(interval);
    qDebug( "World Processor: World processing started, processing interval: %d ms.", interval );

}

void Processor::stop() {

    if (p_timerId) {

        killTimer(p_timerId);
        p_timerId = 0;
        qDebug("World Processor: World processing stoped.");

    }

}

void Processor::timerEvent( QTimerEvent * event ) {

    static const qreal timeStep = 1.0 / 60.0; //need calc
    static const int32 velocityIterations = 6;
    static const int32 positionIterations = 2;

    if ( event->timerId() != p_timerId ) {

//         qWarning("World Processor: detected unknow world processing timer!");

        return;
    }

    p_physicWorld->Step( timeStep, velocityIterations, positionIterations );

    foreach ( AbstractController * controller, controllers )
        controller->advance(timeStep);


    QMapIterator<QUuid,QpwWorld::DecorationObject*> i(objects);
    DecorationObject * object;
//     StaticObject * staticObject;
//     QuasiStaticObject * quasiStaticObject;
    DynamicObject * dynamicObject;
    b2Body * body;
    b2Vec2 vec;

    while ( i.hasNext() ) {

        i.next();
        object = i.value();

        if (!object)
            continue;

        if ( object->baseType() == DecorationObject::DynamicObjectType ) {

            dynamicObject = (DynamicObject*) object;

            if ( !dynamicObject->physicsData() )
                continue;

            body = dynamicObject->physicsData()->body();

            if ( body->IsAwake() ) {

                vec = body->GetPosition();
                send( &QpwPackages::PosChange( i.key(), QPointF( vec.x, vec.y ), body->GetAngle() ) );

            }

        }

//         switch (object->baseType) {

//         case DecorationObject::DecorationObjectType:
//             object
//             break;

//         case DecorationObject::StaticObjectType:
//             staticObject
//             break;

//         case DecorationObject::QuasiStaticObjectType:
//             break;

//         case DecorationObject::DynamicStaticObjectType:
//             break;

//         }

    }

}
